﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Linq;

namespace Bouyei.Geo.GeoParser
{
    using Geometries;

    /// <summary>
    /// 摘自网上源码收集汇总,解析*.shp
    /// </summary>
    public class ShpParser : BaseFileParser
    {
        public ShpInfo ShpInfo { get; private set; }
        public ShpParser(string shpfile)
            : base(shpfile)
        {
            ShpInfo = new ShpInfo();
        }

        public ShpInfo FromReader()
        {
            using (FileStream fs = File.OpenRead(filename))
            using (BinaryReader reader = new BinaryReader(fs,encoding))
            {
                reader.ReadBytes(24);
                byte[] len = reader.ReadBytes(4);//<0代表数据长度未知

                ShpInfo.Length = (len[0] << 24) | (len[1] << 16) | (len[2] << 8) | len[3];
                ShpInfo.Version = reader.ReadInt32();
                ShpInfo.geometryType = (GeoType)reader.ReadInt32();

                ShpInfo.min = new Coordinate(reader.ReadDouble(), reader.ReadDouble());
                ShpInfo.max = new Coordinate(reader.ReadDouble(), reader.ReadDouble());

                int blockCnt = (int)(fs.Length - 100) >> 3;

                reader.ReadBytes(32);

                switch (ShpInfo.geometryType)
                {
                    case GeoType.POINT:
                        {
                            var point = GetPointByBinaryReader(reader);
                            ShpInfo.geometry = new Geometry(point);
                        }
                        break;
                    case GeoType.LINESTRING:
                        {
                            var lineString = GetLineStringByBinaryReader(blockCnt, reader);
                            ShpInfo.geometry = new Geometry(GeoType.LINESTRING, lineString);
                        }
                        break;
                    case GeoType.POLYGON:
                        {
                            var polygons = GetPolygonByBinaryReader(blockCnt, reader);
                            if (polygons.Count == 1)
                            {
                                ShpInfo.geometry = new Geometry(GeoType.POLYGON, polygons[0]);
                            }
                            else if (polygons.Count >= 2)
                            {
                                var coords = polygons.GetRange(1, polygons.Count - 1);
                                ShpInfo.geometry = new Geometry(polygons[0], coords);
                            }
                        }
                        break;
                }
            }

            return ShpInfo;
        }

        private Coordinate GetPointByBinaryReader(BinaryReader reader)
        {
            while (reader.PeekChar() != -1)
            {
                //记录头8字节和4字节地shptype
                reader.ReadBytes(12);

                Coordinate p = new Coordinate()
                {
                    X = reader.ReadDouble(),
                    Y = reader.ReadDouble()
                };

                return p;
            }

            return null;
        }

        private Coordinate[] GetLineStringByBinaryReader(int blockCnt, BinaryReader reader)
        {
            List<ShpPolyLine> lines = new List<ShpPolyLine>(blockCnt);
            while (reader.PeekChar() != -1)
            {
                uint RecordNum = reader.ReadUInt32();

                int DataLength = reader.ReadInt32();

                reader.ReadInt32();

                ShpPolyLine line = new ShpPolyLine();
                line.Box[0] = reader.ReadDouble();
                line.Box[1] = reader.ReadDouble();
                line.Box[2] = reader.ReadDouble();
                line.Box[3] = reader.ReadDouble();

                line.NumParts = reader.ReadInt32();
                line.NumPoints = reader.ReadInt32();
                if (line.NumPoints == 0) continue;

                line.Parts = new List<int>(line.NumParts);
                line.coordiantes = new Coordinate[line.NumPoints];

                for (int i = 0; i < line.NumParts; ++i)
                {
                    line.Parts.Add(reader.ReadInt32());
                }

                for (int i = 0; i < line.NumPoints; ++i)
                {
                    line.coordiantes[i] = new Coordinate()
                    {
                        X = reader.ReadDouble(),
                        Y = reader.ReadDouble()
                    };
                }

                lines.Add(line);
                break;
            }
            return lines[0].coordiantes;
        }

        private List<Coordinate[]> GetPolygonByBinaryReader(int blockCnt, BinaryReader reader)
        {
            List<ShpPolygon> polygons = new List<ShpPolygon>(blockCnt);

            while (reader.PeekChar() != -1)
            {
                uint RecordNum = reader.ReadUInt32();

                int DataLength = reader.ReadInt32();

                int m = reader.ReadInt32();

                ShpPolygon polygon = new ShpPolygon();
                polygon.Box[0] = reader.ReadDouble();
                polygon.Box[1] = reader.ReadDouble();
                polygon.Box[2] = reader.ReadDouble();
                polygon.Box[3] = reader.ReadDouble();
                polygon.NumParts = reader.ReadInt32();
                polygon.NumPoints = reader.ReadInt32();
                if (polygon.NumPoints == 0) continue;

                polygon.Parts = new List<int>(polygon.NumParts);
                polygon.coordiantes = new Coordinate[polygon.NumPoints];

                for (int i = 0; i < polygon.NumParts; ++i)
                {
                    polygon.Parts.Add(reader.ReadInt32());
                }

                for (int i = 0; i < polygon.NumPoints; ++i)
                {
                    polygon.coordiantes[i] = new Coordinate()
                    {
                        X = reader.ReadDouble(),
                        Y = reader.ReadDouble()
                    };
                }

                polygons.Add(polygon);
            }
            return polygons.Select(x => x.coordiantes).ToList();
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public class ShpPolyLine
    {
        public double[] Box = new double[4];
        public int NumParts;
        public int NumPoints;
        public List<int> Parts;
        public Coordinate[] coordiantes;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class ShpPolygon : ShpPolyLine
    {

    }

    [StructLayout(LayoutKind.Sequential)]
    public class ShpInfo
    {
        public GeoType geometryType { get; set; }

        public int Version { get; set; }

        public int Length { get; set; }

        public Coordinate min { get; set; }

        public Coordinate max { get; set; }

        public Geometry geometry { get; set; }
    }
}
